/*
 * program.c -- this file is part of mISPL.
 * Copyright (C) 2011 Christian Krause
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
#include "program_macros.h"
#include "graph.h"

program_t program_init(u_char registers, u_char length) {
	program_t program = malloc(
			(sizeof(__program_s)) -
			(sizeof(command_t) * MAX_PROGRAM_LENGTH) +
			(sizeof(command_t) * ((int) length)));
	program->registers = registers;
	program->length = length;
	return program;
}

program_t program_init_rand(u_char registers, u_char length) {
	u_char r = (rand() % (registers-1)) + 1;
	u_char l = (rand() % (length-1)) +1;
	return program_init(r,l);
}

void program_delete_command(program_t program, u_char command) {
	int i;
	if (command<0 || command>=program->length) {
		return;
	}
	program->length--;
	for (i=command; i<program->length; i++) {
		program->commands[i] = program->commands[i+1];
	}
}

void program_delete_register(program_t program, u_char reg) {
	if (reg<0 || reg>=program->registers) {
		return;
	}
	program->registers--;
	u_char ins, op1, op2;
	int i;
	for (i=0; i<program->length; i++) {
		ins = GET_INS(program,i);
		op1 = GET_OP1(program,i);
		op2 = GET_OP2(program,i);
		if (op1==reg || (INS_WITH_OP2_REGISTER(ins) && op2==reg)) {
			program_delete_command(program, i--);
		}
		else {
			if (op1>reg) op1--;
			if (op2>reg && INS_WITH_SECOND_OP_REGISTER(ins)) op2--;
			SET_COMMAND(program, i, ins, op1, op2);
		}
	}
}

void program_fill_rand(program_t program) {
	int i, j, random;
	int open_loops_count;
	u_char open_loops[MAX_PROGRAM_REGISTERS];
	u_char ins, op1, op2;
	bool_t reset_ins;
	// generate random instructions first:
	for (i=0; i<program->length; i++) {
		random = rand();
		ins = (u_char) ((random & 0xFF) % INSTRUCTION_COUNT);
		op1 = (u_char) (((random>>8) & 0x0F) % program->registers);
		op2 = (u_char) ((random>>12) & 0x0F);
		SET_COMMAND(program, i, ins, op1, op2);
	}
	// make sure that all loops are closed correctly:
	open_loops_count = 0;
	for (i=0; i<program->length; i++) {
		ins = INSTR(program, i);
		op1 = OP1(program, i);
		op2 = OP2(program, i);
		reset_ins = FALSE;
		if (ins==LOOP) {
			if (open_loops_count==MAX_PROGRAM_REGISTERS) {
				reset_ins = TRUE;
			}
			for (j=0; j<open_loops_count; j++) {
				if (open_loops[j]==op1) {
					reset_ins = TRUE;
				}
			}
			if (!reset_ins) {
				open_loops[open_loops_count++] = op1;
			}
		}
		else if (ins==ENDLOOP) {
			if (open_loops_count==0) {
				reset_ins = TRUE;
			} else {
				open_loops_count--;
			}
		}
		if (reset_ins) {
			ins = rand() % LOOP;
			program->commands[i] = COMMAND(ins, op1, op2);
		}
	}
	i = program->length-1;
	while (open_loops_count>0) {
		ins = INSTR(program, i);
		if (ins==ENDLOOP) {
			ins = rand() % LOOP;
			op1 = OP1(program, i);
			op2 = OP2(program, i);
			program->commands[i] = COMMAND(ins, op1, op2);
			open_loops_count--;
		}
		i--;
	}

	// make sure that within
}

void program_clear(program_t program) {
	free(program);
}

program_t program_copy(program_t program) {
	program_t copy = program_init(program->registers, program->length);
	memcpy(copy->commands, program->commands, program->length * sizeof(command_t));
	return copy;
}

void program_print(program_t program) {
	printf("BEGINPROG %d\n", program->registers);
	u_char instr;
	int i, length = PROGRAM_LENGTH(program);
	for (i=0; i<length; i++) {
		printf("  ");
		if (program->looplength>0) {
			if (i==program->startlength) {
				printf("BEGINLOOP #%d\n  ", REG_COUNTER);
			}
			if (i>=program->startlength && i<START_LOOP_LENGTH(program)) {
				printf("  ");
			}
			if (i==START_LOOP_LENGTH(program)) {
				printf("ENDLOOP\n  ");
			}
		}
		instr = INSTR(program,i);
		if (INSTR_WITH_SECOND_OP_CONSTANT(instr)) {
			printf("%s #%d %d\n",  INSTR_NAMES[instr], OP1(program,i), OP2_CONST(program,i));
		}
		else if (INSTR_WITH_SECOND_OP_REGISTER(instr)) {
			printf("%s #%d #%d\n", INSTR_NAMES[instr], OP1(program,i), OP2(program,i));
		}
		else if (INSTR_WITH_SECOND_OP_NONE(instr)) {
			printf("%s #%d\n", INSTR_NAMES[instr], OP1(program,i));
		}
	}
	if (program->looplength>0 && program->endlength==0) {
		printf("  ENDLOOP\n");
	}
	printf("ENDPROG\n");
}

void program_simplify(program_t program) {
	u_char ins, op1, op2;
	int i, length;
	length = PROGRAM_LENGTH(program);
	// delete useless commands:
	for (i=0; i<length; i++) {
		ins = INSTR(program,i);
		if ((ins==COP && (OP1(program,i)==OP2(program,i))) ||
			(ins==DIV && (OP2_CONST(program,i)==1)) ||
			(ins==ADD && (OP2_CONST(program,i)==0)) ||
			(ins==SUB && (OP2_CONST(program,i)==0))) {
				program_delete_instr(program, i);
				length--; i--;
		}
	}
	// build up the register dependency graph:
	graph_t depgraph = graph_init(program->regcount);
	for (i=0; i<length; i++) {
		ins = INSTR(program,i);
		op1 = OP1(program,i);
		op2 = OP2(program,i);
		if (INSTR_WITH_SECOND_OP_REGISTER(ins)) {
			graph_add_edge(depgraph, op1, op2);
		}
		if (i>=program->startlength && i<START_LOOP_LENGTH(program)) {
			graph_add_edge(depgraph, op1, REG_COUNTER);
		}
	}
	// delete unused registers (in reverse order):
	for (i=program->regcount-1; i>REG_COUNTER; i--) {
		if (!graph_exists_path(depgraph, REG_TARGET, i)) {
			program_delete_reg(program, i);
		}
	}
	// dependency graph is not needed anymore:
	graph_clear(depgraph);
}

sequence_t program_exec(program_t program, block_t history, int maxbits) {
	block_reset(history);
	sequence_t registers = sequence_init(program->regcount);
	mpz_ptr target, source;
	int length = PROGRAM_LENGTH(program);
	int i, j, instr;
	for (i=0; i<history->maxrows; i++) {
		for (j=0; j<length; j++) {
			instr = INSTR(program,j);
			target = registers->values[OP1(program,j)];
			if (INSTR_WITH_SECOND_OP_REGISTER(instr)) {
				source = registers->values[OP2(program,j)];
			}
			switch (instr) {
				case SET:
					mpz_set_si(target, OP2_CONST(program,j) );
					break;
				case COP:
					mpz_set(target, source);
					break;
				case ADD:
					mpz_add(target, target, source);
					break;
				case SUB:
					mpz_sub(target, target, source);
					break;
				case MUL:
					mpz_mul(target, target, source);
					break;
				case DIV:
					//mpz_div(target, target, source);
					break;
				case MOD:
					//mpz_mod(target, target, source);
					break;
				case POW:
					//mpz_pow(target, target, source);
					break;
				case RAD:
					//mpz_root(target, target, source);
					break;
				case LOG:
//					mpz_l(target, target, source);
//					break;
				case BNE:
					//...
					//mpz_root(target, target, source);
					break;
				case SIG:
					//mpz_neg(target, target, source);
					break;
				case ABS:
					//mpz_neg(target, target, source);
					break;
				case LGN:
					//mpz_neg(target, target, source);
					break;
				default:
					printf("Unsupported instruction: %s\n", INSTR_NAMES[instr]);
					return 0;
			}
		}
		// check whether any of the numbers got too big:
		for (j=0; j<program->regcount; j++) {
			if (mpz_sizeinbase(registers->values[j],2)>maxbits) {
				goto FINISH;
			}
		}
		// otherwise try to add the current state to the history:
		hashcode_t hashcode = sequence_hashcode(registers);
		if (block_contains_row(history, registers, hashcode)) {
			goto FINISH;
		}
		block_add_row(history, registers, hashcode);
	}
	// extract the result out of the history:
	FINISH:
	return block_get_column(history, REG_TARGET);
}
